﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
using System.Windows.Forms;
using ICSharpCode.TextEditor;
using System.Drawing;
using tiDE.Runtime;
using ICSharpCode.TextEditor.Document;
using System.Xml.Linq;
using System.IO;
using tiDE.API.Utility;
using System.Threading;
using tiDE.SourceInsight;

namespace tiDE.CodeCompletion
{
    public class z80CodeCompletion : IInternalCompletionProvider
    {
        public char[] CodeCompletionTriggers
        {
            get
            {

                return " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".ToCharArray();
            }
        }

        public int DefaultIndex
        {
            get
            {
                return 0;
            }
        }

        static XDocument z80InstructionSet;
        static Dictionary<string, Dictionary<string, DocumentedEquate>> DocumentedIncludeFiles;

        public ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped)
        {
            try
            {
                if (z80InstructionSet == null)
                {
                    z80InstructionSet = XDocument.Load(File.Open(RuntimeData.tiDEDirectory + "CodeCompletion\\z80InstructionSet.xml", FileMode.Open));
                }
                List<ICompletionData> CompletionData = new List<ICompletionData>();
                LineSegment segment = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
                string line = textArea.Document.TextContent.Substring(segment.Offset, segment.Length);
                int index = textArea.Caret.Offset - segment.Offset;
                int lineLength = line.Length;
                line = line.TrimStart();
                if (line.StartsWith(".") || line.StartsWith("#"))
                    line = line.Substring(1);
                if (line.StartsWith("define") || line.StartsWith("include"))
                    return new ICompletionData[0];
                if (line.Contains("("))
                    line = line.Substring(line.IndexOf("(") + 1);
                index -= (lineLength - line.Length);
                if (line.Contains(","))
                {
                    try
                    {
                        line = line.Remove(index);
                        if (line.Contains(","))
                            line = line.Substring(line.IndexOf(","));
                    }
                    catch { }
                }
                line = line.Replace(")", "");
                line = line.Trim();
                line += charTyped;
                foreach (XElement element in z80InstructionSet.Root.Elements("code"))
                {
                    z80CodeCompletionData data = new z80CodeCompletionData();
                    data.Description = element.Attribute("text").Value + "\nDescription:\n" + element.Value.Replace("      ", " ");
                    if (element.Attribute("flags") != null)
                    {
                        data.Description += "\n\nFlags:\n" + element.Attribute("flags").Value.Replace("|", "\n");
                    }
                    if (element.Attribute("opcode") != null)
                    {
                        data.Description += "\n\nOP-Code:\n" + element.Attribute("opcode").Value.Replace("      ", " ");
                    }
                    if (element.Attribute("tstates") != null)
                    {
                        data.Description += "\n\nT-States:\n" + element.Attribute("tstates").Value.Replace("|", "\n");
                    }
                    if (element.Attribute("size") != null)
                    {
                        data.Description += "\n\nSize:\n" + element.Attribute("size").Value;
                    }
                    if (element.Attribute("actualValue") != null)
                    {
                        data.ActualValue = element.Attribute("actualValue").Value;
                        data.CaretOffset = data.ActualValue.Length - 1;
                    }
                    if (element.Attribute("caretStop") != null)
                    {
                        data.CaretOffset = int.Parse(element.Attribute("caretStop").Value);
                    }
                    data.ImageIndex = 0;
                    data.Text = element.Attribute("text").Value;
                    data.Priority = -data.Text.Length;
                    if (data.Text == line)
                        data.Priority = 1;
                    if (data.Text.ToLower().StartsWith(line.ToLower()))
                    {
                        CompletionData.Add(data);
                    }
                }
                if (CompletionData.Count == 0 && string.IsNullOrEmpty(line))
                {
                    CompletionData.Add(new z80CodeCompletionData()
                    {
                        Description = "Inserts a range of bytes into the file.",
                        Text = "db ",
                        ActualValue = "db ",
                        CaretOffset = 3,
                        ImageIndex = 0,
                        Priority = 0,
                    });
                    CompletionData.Add(new z80CodeCompletionData()
                    {
                        Description = "Inserts a range of 16-bit little-endian words into the file.",
                        Text = "dw ",
                        ActualValue = "dw ",
                        CaretOffset = 3,
                        ImageIndex = 0,
                        Priority = 0,
                    });
                }
                if (Assembler.SavedIncludeFiles != null)
                {
                    foreach (KeyValuePair<string, SavedInclude> pair in Assembler.SavedIncludeFiles)
                    {
                        foreach (Macro macro in pair.Value.Macros)
                        {
                            if (macro.Match.ToLower().StartsWith(line.ToLower()))
                            {
                                string macroMatch = macro.Match;
                                int caretOffset = 0;
                                if (macro.IsTasm)
                                    macroMatch += "(";
                                caretOffset = macroMatch.Length;
                                for (int i = 1; i < macro.Arguments.Length; i++)
                                {
                                    macroMatch += ",";
                                }
                                if (macro.IsTasm)
                                    macroMatch += ")";
                                string macroDescription = "Macro: " + macro.Match + "\nValue: " + macro.Value + "\nFile: " + DataConverter.StripDirectory(pair.Key);
                                CompletionData.Add(new z80CodeCompletionData()
                                {
                                    Text = macro.Match,
                                    Description = macroDescription,
                                    ActualValue = macroMatch,
                                    CaretOffset = caretOffset,
                                    ImageIndex = 0,
                                    Priority = 0,
                                });
                            }
                        }
                    }
                }
                if (Assembler.SavedIncludeFiles != null)
                {
                    foreach (KeyValuePair<string, SavedInclude> pair in Assembler.SavedIncludeFiles)
                    {
                        if (DocumentedIncludeFiles == null)
                            DocumentedIncludeFiles = new Dictionary<string, Dictionary<string, DocumentedEquate>>();
                        else
                        {
                            if (!DocumentedIncludeFiles.ContainsKey(pair.Key))
                            {
                                ParameterizedThreadStart start = new ParameterizedThreadStart(ConstructDocumentation);
                                start.Invoke(pair.Key);
                            }
                        }
                        foreach (KeyValuePair<string, Assembler.Constant> constPair in pair.Value.Constants)
                        {
                            if (constPair.Key.ToLower().StartsWith(line.ToLower()))
                            {
                                string equateDescription = "Equate: " + constPair.Value.Value + "\nFile: " + DataConverter.StripDirectory(pair.Key) + "\n\n(The documentation cache is still being constructed for this file)";
                                if (DocumentedIncludeFiles.ContainsKey(pair.Key))
                                {
                                    if (DocumentedIncludeFiles[pair.Key].ContainsKey(constPair.Key))
                                    {
                                        equateDescription = DocumentedIncludeFiles[pair.Key][constPair.Key].Documentation +
                                            "\n\nMore info: " + DocumentedIncludeFiles[pair.Key][constPair.Key].InfoUrl;
                                    }
                                }
                                CompletionData.Add(new z80CodeCompletionData()
                                {
                                    Text = constPair.Value.Name,
                                    Description = equateDescription,
                                    ActualValue = constPair.Value.Name,
                                    CaretOffset = constPair.Value.Name.Length,
                                    ImageIndex = 0,
                                    Priority = 0,
                                });
                            }
                        }
                    }
                }
                foreach (string label in z80SourceInsight.GetLocalLabels(textArea.Document.TextContent))
                {
                    if (label.ToLower().StartsWith(line.ToLower()))
                    {
                        CompletionData.Add(new z80CodeCompletionData()
                        {
                            ActualValue = label,
                            Description = label + "\n\nLocal Label",
                            CaretOffset = label.Length,
                            ImageIndex = 0,
                            Priority = 0,
                            Text = label,
                        });
                    }
                }
                return CompletionData.ToArray();
            }
            catch
            {
                return new ICompletionData[0];
            }
        }

        private void ConstructDocumentation(object IncludeFileObj)
        {
            string IncludeFile = (string)IncludeFileObj;
            // Runs in seperate thread
            Dictionary<string, DocumentedEquate> equates = new Dictionary<string, DocumentedEquate>();
            if (File.Exists(IncludeFile + ".xml"))
            {
                // Generate cache
                Stream docStream = File.Open(IncludeFile + ".xml", FileMode.Open);
                XDocument document = XDocument.Load(docStream);
                docStream.Close();
                foreach (XElement element in document.Root.Elements("symbol"))
                {
                    equates.Add(element.Attribute("name").Value, new DocumentedEquate()
                    {
                        Documentation = element.Value,
                        InfoUrl = element.Attribute("moreinfo").Value,
                    });
                }
            }
            DocumentedIncludeFiles.Add(IncludeFile, equates);
        }

        private bool CheckContains(List<ICompletionData> CompletionData, string opcodeFriendly)
        {
            foreach (ICompletionData completionData in CompletionData)
            {
                if (completionData.Text == opcodeFriendly)
                    return true;
            }
            return false;
        }

        public ImageList ImageList
        {
            get
            {
                System.Windows.Forms.ImageList imageList = new ImageList();
                imageList.Images.Add(global::tiDE.Properties.Resources.stock_compile);
                return new ImageList();
            }
        }

        public bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key)
        {
            try
            {
                textArea.Caret.Position = textArea.Document.OffsetToPosition(insertionOffset);
                string newContent = textArea.Document.TextContent;
                LineSegment segment = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
                string line = textArea.Document.TextContent.Substring(segment.Offset, segment.Length);
                int index = textArea.Caret.Offset - segment.Offset;
                int length = line.Length;
                line = line.Trim();
                if (line.Contains("("))
                    line = line.Substring(line.IndexOf("(") + 1);
                if (line.Contains(","))
                    line = line.Substring(line.Remove(index).IndexOf(",") + 1);
                line = line.Trim();
                index -= (length - line.Length);
                z80CodeCompletionData z80Data = data as z80CodeCompletionData;
                newContent = newContent.Remove(textArea.Caret.Offset - index, index);
                newContent = newContent.Insert(textArea.Caret.Offset - index, z80Data.ActualValue);
                textArea.Document.TextContent = newContent;
                textArea.Caret.Position = textArea.Document.OffsetToPosition(segment.Offset + (length - line.Length) + z80Data.CaretOffset);
            }
            catch { }
            return false;
        }

        public string PreSelection
        {
            get
            {
                return null;
            }
        }

        public CompletionDataProviderKeyResult ProcessKey(char key)
        {
            return CompletionDataProviderKeyResult.NormalKey;
        }

        public string GetToolTip(TextLocation TextLocation, ICSharpCode.TextEditor.Document.IDocument Document)
        {
            int index = Document.PositionToOffset(TextLocation);
            LineSegment thisLine = Document.GetLineSegmentForOffset(index);
            string hoverWord;
            try
            {
                hoverWord = thisLine.GetWord(TextLocation.Column).Word;
            }
            catch
            {
                return "";
            }
            if (Assembler.SavedIncludeFiles != null)
            {
                foreach (KeyValuePair<string, SavedInclude> pair in Assembler.SavedIncludeFiles)
                {
                    if (DocumentedIncludeFiles == null)
                        DocumentedIncludeFiles = new Dictionary<string, Dictionary<string, DocumentedEquate>>();
                    else
                    {
                        if (!DocumentedIncludeFiles.ContainsKey(pair.Key))
                        {
                            ParameterizedThreadStart start = new ParameterizedThreadStart(ConstructDocumentation);
                            start.Invoke(pair.Key);
                        }
                    }
                    if (DocumentedIncludeFiles.ContainsKey(pair.Key))
                    {
                        foreach (var constPair in pair.Value.Constants)
                        {
                            if (hoverWord.ToLower() == constPair.Key.ToLower())
                            {
                                string equateDescription = "Equate: " + constPair.Value.Value + "\nFile: " + DataConverter.StripDirectory(pair.Key) +
                                    "\n\n(The documentation cache is still being constructed for this file)";
                                if (DocumentedIncludeFiles.ContainsKey(pair.Key))
                                {
                                    if (DocumentedIncludeFiles[pair.Key].ContainsKey(constPair.Key))
                                    {
                                        equateDescription = DocumentedIncludeFiles[pair.Key][constPair.Key].Documentation +
                                            "\n\nMore info: " + DocumentedIncludeFiles[pair.Key][constPair.Key].InfoUrl;
                                    }
                                }
                                return equateDescription;
                            }
                        }
                    }
                }
            }
            if (z80InstructionSet == null)
            {
                z80InstructionSet = XDocument.Load(File.Open(RuntimeData.tiDEDirectory + "CodeCompletion\\z80InstructionSet.xml", FileMode.Open));
            }
            foreach (XElement element in z80InstructionSet.Root.Elements("code"))
            {
                string Description = element.Attribute("text").Value + "\nDescription:\n" + element.Value.Replace("      ", " ");
                if (element.Attribute("flags") != null)
                {
                    Description += "\n\nFlags:\n" + element.Attribute("flags").Value.Replace("|", "\n");
                }
                if (element.Attribute("opcode") != null)
                {
                    Description += "\n\nOP-Code:\n" + element.Attribute("opcode").Value.Replace("      ", " ");
                }
                if (element.Attribute("tstates") != null)
                {
                    Description += "\n\nT-States:\n" + element.Attribute("tstates").Value.Replace("|", "\n");
                }
                if (element.Attribute("size") != null)
                {
                    Description += "\n\nSize:\n" + element.Attribute("size").Value;
                }
                string Text = element.Attribute("text").Value;
                if (Text.ToLower() == hoverWord.ToLower())
                {
                    return Description;
                }
            }
            return "";
        }
    }

    public class DocumentedEquate
    {
        public string Documentation { get; set; }
        public string InfoUrl { get; set; }
    }

    public class z80CodeCompletionData : ICompletionData
    {
        public string Description { get; set; }

        public int ImageIndex { get; set; }

        public string ActualValue { get; set; }

        public int CaretOffset { get; set; }

        public bool InsertAction(TextArea textArea, char ch)
        {
            return false;
        }

        public double Priority { get; set; }

        public string Text { get; set; }
    }
}
